﻿using ClickHouse.Client.ADO;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Dapper.Sharding
{
    internal class ClickHouseDatabase : IDatabase
    {
        public ClickHouseDatabase(string name, ClickHouseClient client) : base(name, client)
        {
            ConnectionString = ConnectionStringBuilder.BuilderClickHouse(client.Config, name);
        }

        public override string ConnectionString { get; set; }

        public override void DropTable(string name)
        {
            Execute($"DROP TABLE IF EXISTS {name}");
            TableCache.TryRemove(name, out _);
        }

        public override bool ExistsTable(string name)
        {
            return !string.IsNullOrEmpty(QueryFirstOrDefault<string>($"SHOW TABLES LIKE '{name}'"));
        }

        public override async Task<bool> ExistsTableAsync(string name)
        {
            return !string.IsNullOrEmpty(await QueryFirstOrDefaultAsync<string>($"SHOW TABLES LIKE '{name}'"));
        }

        public override DbConnection GetConn()
        {
            var conn = new ClickHouseConnection(ConnectionString);
            if (conn.State == ConnectionState.Closed)
            {
                try
                {
                    conn.Open();
                }
                catch
                {
                    conn.Dispose();
                    throw;
                }
            }
            return conn;
        }

        public override async Task<DbConnection> GetConnAsync()
        {
            var conn = new ClickHouseConnection(ConnectionString);
            if (conn.State == ConnectionState.Closed)
            {
                try
                {
                    await conn.OpenAsync();
                }
                catch
                {
#if NET462
                    conn.Dispose();
#else
                    await conn.CloseAsync();
#endif

                    throw;
                }
            }
            return conn;
        }

        public override List<string> GetTableColumnList(string name)
        {
            return Query($"DESCRIBE TABLE {name}").Select(s => (string)s.name).ToList();
        }

        public override async Task<List<string>> GetTableColumnListAsync(string name)
        {
            var list = await QueryAsync($"DESCRIBE TABLE {name}");
            return list.Select(s => (string)s.name).ToList();
        }

        public override TableEntity GetTableEntityFromDatabase(string name, bool firstCharToUpper = false)
        {
            var entity = new TableEntity();
            var manager = GetTableManager(name);
            entity.ColumnList = manager.GetColumnEntityList(null, firstCharToUpper);
            return entity;
        }

        public override List<string> GetTableList()
        {
            return Query<string>("SHOW TABLES");
        }

        public override ITableManager GetTableManager(string name)
        {
            return new ClickHouseTableManager(name, this);
        }

        public override string GetTableScript<T>(string name)
        {
            var tableEntity = ClassToTableEntityUtils.Get<T>(DbType, DbVersion);
            var sb = new StringBuilder();
            if (string.IsNullOrEmpty(tableEntity.Cluster))
            {
                sb.Append($"CREATE TABLE IF NOT EXISTS {name} (");
            }
            else
            {
                sb.Append($"CREATE TABLE IF NOT EXISTS {name} ON CLUSTER {tableEntity.Cluster} (");
            }
            foreach (var item in tableEntity.ColumnList)
            {
                sb.Append($"{item.Name} {item.DbType}");
#if CORE6
                if (item.CsType.IsValueType && item.CsType != typeof(DateTime) && item.CsType != typeof(DateTimeOffset) && item.CsType != typeof(DateOnly) && item.CsType != typeof(TimeOnly) && item.CsType != typeof(DateTime?) && item.CsType != typeof(DateTimeOffset?) && item.CsType != typeof(DateOnly?) && item.CsType != typeof(TimeOnly?))
#else
                if (item.CsType.IsValueType && item.CsType != typeof(DateTime) && item.CsType != typeof(DateTimeOffset) && item.CsType != typeof(DateTime?) && item.CsType != typeof(DateTimeOffset?))
#endif
                {
                    if (!item.DbType.ToLower().EndsWith("default 0"))
                    {
                        sb.Append(" DEFAULT 0");
                    }
                }
                sb.Append($" COMMENT '{item.Comment}'");
                if (item != tableEntity.ColumnList.Last())
                {
                    sb.Append(',');
                }
            }
            sb.Append($")ENGINE={tableEntity.Engine}");
            return sb.ToString();
        }

        public override void OptimizeTable(string name, bool final = false, bool deduplicate = false)
        {
            var sql = $"OPTIMIZE TABLE {name}";
            if (final)
            {
                sql += " FINAL";
            }
            if (deduplicate)
            {
                sql += " DEDUPLICATE";
            }
            Execute(sql);
        }

        public override void OptimizeTable(string name, string partition, bool final = false, bool deduplicate = false)
        {
            var sql = $"OPTIMIZE TABLE {name} PARTITION {partition}";
            if (final)
            {
                sql += " FINAL";
            }
            if (deduplicate)
            {
                sql += " DEDUPLICATE";
            }
            Execute(sql);
        }

        public override void SetTableComment(string name, string comment)
        {
            Execute($"ALTER TABLE {name} MODIFY COMMENT '{comment}'");
        }

        public override void SetTablespace(string table, string tablespace)
        {
            throw new NotImplementedException();
        }

        public override Task SetTablespaceAsync(string table, string tablespace)
        {
            throw new NotImplementedException();
        }

        public override void TruncateTable(string name)
        {
            throw new NotImplementedException();
        }

        public override Task TruncateTableAsync(string name, CancellationToken cancellationToken = default)
        {
            throw new NotImplementedException();
        }

        public override void Vacuum()
        {
            throw new NotImplementedException();
        }

        protected override ITable<T> CreateITable<T>(string name)
        {
            return new ClickHouseTable<T>(name, this);
        }
    }
}
